using FastDevTemplate.DataAccess.Base;
using FastDevTemplate.DataAccess.Config;
using FastDevTemplate.DataAccess.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Configuration;

namespace FastDevTemplate.DataAccess;

public class FastDevTemplateDbContext : DbContext
{
    // private readonly IConfiguration _configuration;
    private readonly string _connectionString;
    
    public FastDevTemplateDbContext()
    {
        _connectionString = ConfigReader.ConnectionString;
    }
    
    public FastDevTemplateDbContext(IConfiguration configuration)
    {
        var connectionString = configuration.GetConnectionString("ConnectionString");
        if (String.IsNullOrEmpty(connectionString))
            throw new Exception("Connection String is Empty.");
        _connectionString = connectionString;
    }
    
    // enter:
    public DbSet<User>? Users { get; set; }
    public DbSet<Role>? Roles { get; set; }
    public DbSet<Permission>? Permissions { get; set; }
    public DbSet<Log>? Logs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseMySql(_connectionString,
            new MySqlServerVersion(new Version(8, 0)),
            m =>
            {
                // m.UseMicrosoftJson(MySqlCommonJsonChangeTrackingOptions.FullHierarchyOptimizedSemantically);
                // m.EnableRetryOnFailure();
            }
        );
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new PermissionConfiguration());
        modelBuilder.ApplyConfiguration(new RolePermissionConfiguration());
        modelBuilder.ApplyConfiguration(new RoleConfiguration());
        modelBuilder.ApplyConfiguration(new UserRoleConfiguration());
        modelBuilder.ApplyConfiguration(new UserConfiguration());
        modelBuilder.ApplyConfiguration(new LogConfiguration());
    }

    #region Audit Persistence

    public override int SaveChanges()
    {
        OnChange();
        return base.SaveChanges();
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        OnChange();
        return await base.SaveChangesAsync(cancellationToken);
    }

    public override EntityEntry Remove(object entity)
    {
        if (entity is IAuditEntity auditEntity)
        {
            auditEntity.ToDeleteState();
            Entry(entity).State = EntityState.Modified;
            return base.Entry(entity);
        }

        return base.Remove(entity);
    }

    public override EntityEntry<TEntity> Remove<TEntity>(TEntity entity)
    {
        if (entity is IAuditEntity auditEntity)
        {
            auditEntity.ToDeleteState();
            Entry(entity).State = EntityState.Modified;
            return base.Entry(entity);
        }

        return base.Remove(entity);
    }

    public override void RemoveRange(params object[] entities)
    {
        foreach (var entity in entities)
            if (entity is IAuditEntity auditEntity)
            {
                auditEntity.ToDeleteState();
                Entry(entity).State = EntityState.Modified;
            }
            else
            {
                Entry(entity).State = EntityState.Deleted;
            }
    }

    public override void RemoveRange(IEnumerable<object> entities)
    {
        foreach (var entity in entities)
            if (entity is IAuditEntity auditEntity)
            {
                auditEntity.ToDeleteState();
                Entry(entity).State = EntityState.Modified;
            }
            else
            {
                Entry(entity).State = EntityState.Deleted;
            }
    }

    private void OnChange()
    {
        var entities = ChangeTracker
            .Entries()
            .Where(x => x.State == EntityState.Added || x.State == EntityState.Modified)
            .Where(e => e.Entity is IAuditEntity);

        foreach (var entity in entities)
        {
            if (entity.State == EntityState.Added) ((IAuditEntity)entity.Entity).ToCreateState();

            if (entity.State == EntityState.Modified) ((IAuditEntity)entity.Entity).ToUpdateState();
        }
    }

    #endregion
}